﻿using GodSharp.Communications.Abstractions;
using GodSharp.SerialPort;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;

namespace GodSharp.Communications.SerialPort
{
    /// <summary>
    /// 
    /// </summary>
    /// <param name="destination"></param>
    /// <param name="buffer"></param>
    /// <param name="sender"></param>
    internal delegate void SerialPortCommunicationDataEventHandler(string destination, byte[] buffer, GodSerialPort sender);

#pragma warning disable CS1584 // XML comment has syntactically incorrect cref attribute
#pragma warning disable CS1658 // Warning is overriding an error
    /// <summary>
    /// SerialPortCommunicationProvider
    /// </summary>
    /// <seealso cref="GodSharp.Communications.Abstractions.CommunicationProviderBase{GodSharp.Communications.SerialPort.SerialPortCommunicationProviderOptions}" />
    /// <seealso cref="GodSharp.Communications.Abstractions.ICommunicationProvider" />
    [NameDescription("SerialPortCommunicationProvider", "SerialPort protocol for GodSharp.Communications")]
#pragma warning restore CS1658 // Warning is overriding an error
#pragma warning restore CS1584 // XML comment has syntactically incorrect cref attribute
    public sealed class SerialPortCommunicationProvider : CommunicationProviderBase<SerialPortCommunicationProviderOptions, ISerialPortCommunicationHandler>, ISerialPortCommunicationProvider
    {
        private Dictionary<string, SerialPortCommunicationDataEventHandler> OnData { get; set; }

        Dictionary<int, GodSerialPort> clients = null;
        SerialPortConfig[] configs = null;

        Dictionary<int, GodSerialPort> senders = null;

        bool initialized = false;
        bool started = false;

        /// <summary>
        /// Initializes a new instance of the <see cref="SerialPortCommunicationProvider"/> class.
        /// </summary>
        public SerialPortCommunicationProvider()
        {
            this.OnData = new Dictionary<string, SerialPortCommunicationDataEventHandler>();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SerialPortCommunicationProvider"/> class.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        public SerialPortCommunicationProvider(SerialPortCommunicationProviderOptions parameter)
        {
            Initialize(parameter);
        }

        /// <summary>
        /// Initializes the specified parameter.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException">
        /// parameter
        /// or
        /// ServerClientConfigs
        /// </exception>
        /// <exception cref="InvalidOperationException">The provider is initialized</exception>
        /// <exception cref="ArgumentException">
        /// Configs
        /// or
        /// Configs
        /// </exception>
        private bool Initialize(SerialPortCommunicationProviderOptions parameter)
        {
            if (parameter == null) throw new ArgumentNullException(nameof(parameter));
            
            parameter.Configs = parameter.Configs?.Where(x => x.Available)?.ToArray();

            if (parameter == null || parameter.Configs.Count < 1) throw new ArgumentNullException(nameof(parameter));

            if (initialized) throw new InvalidOperationException("The provider is initialized");

            if (parameter.Configs.GroupBy(x => x.Id).Any(x => x.ToArray().Length > 1)) throw new ArgumentException($"Duplicate 'Id' at {nameof(parameter.Configs)}");

            configs = parameter.Configs.ToArray();
            
            if (configs?.Length > 0)
            {
                clients = new Dictionary<int, GodSerialPort>();

                foreach (var item in configs)
                {
                    if (clients.ContainsKey(item.Id)) continue;

                    clients.Add(item.Id, new GodSerialPort(item.PortName, item.BaudRate,item.ParityBits,item.DataBits,item.StopBits.ToString()) { OnData = SerialDataHandler });
                }
            }

            StateFlag = CommunicationModuleStateFlags.Initialized;

            initialized = true;

            return initialized;
        }

        /// <summary>
        /// Starts the specified objs.
        /// </summary>
        /// <param name="ids">The id array.</param>
        /// <returns></returns>
        public bool Start(params int[] ids)
        {
            CheckingState(true, false, true);
            
            //bool error = false;
            
            List<int> keys = new List<int>();

            if (ids?.Length > 0)
            {
                if (clients?.Count > 0) keys = clients.Keys.Where(x => ids.Contains(x)).ToList();
            }
            else
            {
                keys = clients?.Keys.ToList();
            }

            if (keys?.Count > 0)
            {
                foreach (var key in keys)
                {
                    try
                    {
                        clients[key].Open();

                        if (!clients[key].IsOpen)
                        {
                            //error = true;
                            break;
                        }
                    }
                    catch (Exception ex)
                    {
                        //error = true;
                        Console.WriteLine(ex.Message);
                    }
                }
            }

            return started;
        }

        /// <summary>
        /// Stops the specified objs.
        /// </summary>
        /// <param name="ids">The id array.</param>
        /// <returns></returns>
        public bool Stop(params int[] ids)
        {
            CheckingState(true, true);

            if (clients?.Count > 0)
            {
                List<string> keys = new List<string>();

                foreach (var item in clients)
                {
                    try
                    {
                        if (!item.Value.IsOpen) continue;

                        item.Value.Close();
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                }
            }

            return true;
        }

        /// <summary>
        /// Res the start.
        /// </summary>
        /// <param name="ids">The id array.</param>
        /// <returns></returns>
        public bool Restart(params int[] ids)
        {
            CheckingState(true, true);

            Stop();

            Start();

            return true;
        }

        /// <summary>
        /// Healthes the specified ids.
        /// </summary>
        /// <param name="ids">The ids.</param>
        /// <returns></returns>
        public IDictionary<int, CommunicationModuleStateFlags> Health(params int[] ids)
        {
            IDictionary<int, CommunicationModuleStateFlags> flags = null;
            
            if (clients?.Count > 0)
            {
                IDictionary<int, GodSerialPort> _clients = (ids.Length == 0 ? clients : clients.Where(x => ids.Contains(x.Key)).ToDictionary(x => x.Key, x => x.Value));

                if (_clients?.Count > 0)
                {
                    flags = new Dictionary<int, CommunicationModuleStateFlags>();

                    foreach (var item in _clients)
                    {
                        try
                        {
                            flags.Add(item.Key, item.Value.IsOpen ? CommunicationModuleStateFlags.Started : CommunicationModuleStateFlags.Stopped);
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine(ex.Message);
                        }
                    }
                }
            }

            return flags;
        }

        /// <summary>
        /// Serial the data handler.
        /// </summary>
        /// <param name="client">The client.</param>
        /// <param name="buffer">The buffer.</param>
        private void SerialDataHandler(GodSerialPort client, byte[] buffer)
        {
            try
            {
                Console.WriteLine($"SerialPort:{client.PortName} Received");

                SerialPortConfig config = GetSerialPortConfig(client);

                SocketDataHandler(config, client, buffer);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        /// <summary>
        /// Sockets the data handler.
        /// </summary>
        /// <param name="config">The configuration.</param>
        /// <param name="client">The client.</param>
        /// <param name="buffer">The buffer.</param>
        private void SocketDataHandler(SerialPortConfig config, GodSerialPort client, byte[] buffer)
        {
            if (config == null) return;

            OnData[config.EntryPoint]?.Invoke(config.EntryPoint, buffer, client);
        }

        /// <summary>
        /// Gets the serial port configuration.
        /// </summary>
        /// <param name="client">The client.</param>
        /// <returns></returns>
        private SerialPortConfig GetSerialPortConfig(GodSerialPort client) => configs?.FirstOrDefault(x => x.PortName.Equals(client.PortName, StringComparison.CurrentCultureIgnoreCase));
        
        /// <summary>
        /// Checkings the state.
        /// </summary>
        /// <param name="initialized">if set to <c>true</c> [initialized].</param>
        /// <param name="started">if set to <c>true</c> [started].</param>
        /// <param name="restarting">if set to <c>true</c> [restarting].</param>
        /// <exception cref="System.InvalidOperationException">
        /// The module is not initialized
        /// or
        /// The module is not started
        /// or
        /// The module is running
        /// </exception>
        private void CheckingState(bool initialized = false, bool started = false, bool restarting = false)
        {
            if (initialized && !this.initialized) throw new InvalidOperationException("The provider is not initialized");

            if (started && !this.started) throw new InvalidOperationException("The provider is not started");

            if (restarting && this.started) throw new InvalidOperationException("The provider is running");
        }

        /// <summary>
        /// Registers the modules.
        /// </summary>
        /// <param name="types">The types.</param>
        public void RegisterModules(Type[] types)
        {
            OnRegisterModules(types, (entry, handler) =>
            {
                if (!this.OnData.ContainsKey(entry)) this.OnData.Add(entry, handler.OnSerialPortDataHandler);
                else this.OnData[entry] += handler.OnSerialPortDataHandler;
            });
        }

        /// <summary>
        /// Gets the SerialPort.
        /// </summary>
        /// <param name="id">The identifier.</param>
        /// <returns></returns>
        public GodSerialPort GetSerialPort(int id)
        {
            if (clients.ContainsKey(id)) return clients[id];

            return null;
        }
    }
}